#pragma once

#include "Barrier.h"
#include "LockedDeque.h"

namespace android {

template<typename T>
class BufferQueue {

public:
    BufferQueue(size_t queue_size, size_t buffer_size)
        : mSize(queue_size),
          mBufferSize(buffer_size)
    {
        for (int i = 0; i < queue_size; i++) {
            T *buf = new T(buffer_size);
            enqueueProducerBuffer(buf);
        }
        queue.clear();
    }

    ~BufferQueue() {
        free_queue.free();
        queue.free();
    }

    T* front() {
        if (queue.empty())
            return NULL;
        return queue.front();
    }

    void enqueueConsumerBuffer(T* buf) {
        queue.push_back(buf);
        mConsumerBarrier.open();
    }

    T* dequeueConsumerBuffer() {
        if (queue.empty()) {
            mConsumerBarrier.wait();
        }

        T* buf = queue.front();
        queue.pop_front();
        mConsumerBarrier.close();

        return buf;
    }

    void enqueueProducerBuffer(T *buf) {
        free_queue.push_back(buf);
        mProducerBarrier.open();
    }

    T* dequeueProducerBuffer() {
        if (free_queue.empty()) {
            mProducerBarrier.wait();
        }

        T* buf = free_queue.front();
        free_queue.pop_front();
        mProducerBarrier.close();

        return buf;
    }

    void forceReleaseConsumerBuffer() {
        if (!queue.empty())
            return;

        // queue is empty, so free_queue is not empty
        T* buf = free_queue.front();
        free_queue.pop_front();
        mProducerBarrier.close();

        queue.push_back(buf);
        mConsumerBarrier.open();
    }

    void forceReleaseProducerBuffer() {
        if (!free_queue.empty())
            return;

        // free_queue is empty, so queue is not empty
        T* buf = queue.front();
        queue.pop_front();
        mConsumerBarrier.close();

        free_queue.push_back(buf);
        mProducerBarrier.open();
    }

    void reset() {
        while(!queue.empty()) {
            T* buf = queue.front();
            queue.pop_front();
            mConsumerBarrier.close();

            free_queue.push_back(buf);
            mProducerBarrier.open();
        }

        int size = free_queue.size();
        if (size < mSize) {
            ALOGV("BufferQueue, reset, free queue size %d, less than %d \n", size, mSize);

            int count = mSize - size;
            for (int i = 0; i < count; i++) {
                T *buf = new T(mBufferSize);
                enqueueProducerBuffer(buf);
            }
        }
    }

    size_t getProducerSize() {
        return free_queue.size();
    }

    size_t getConsumerSize() {
        return queue.size();
    }

private:
    LockedDeque<T*> free_queue;
    LockedDeque<T*> queue;

    Barrier mProducerBarrier;
    Barrier mConsumerBarrier;

    size_t mSize;
    size_t mBufferSize;
};

}//namespace


